//--------------------------------------------------------------------------------
// This file is part of the Windows Workflow Foundation Sample Code
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// This source code is intended only as a supplement to Microsoft
// Development Tools and/or on-line documentation.  See these other
// materials for detailed information regarding Microsoft code samples.
// 
// THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//--------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Security.Permissions;
using System.Security.Principal;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Threading;
using Microsoft.Samples.Workflow.UI.Design;
using System.Diagnostics;

namespace Microsoft.Samples.Workflow.UI
{
    [ToolboxItem(false)]
    [Designer(typeof(NavigatorWorkflowDesigner), typeof(IDesigner))]
    [ActivityValidator(typeof(NavigatorWorkflowValidator))]
    public class NavigatorWorkflow : Navigator, IActivityEventListener<QueueEventArgs>
    {
        public static readonly DependencyProperty TimeoutDurationProperty = DependencyProperty.Register("TimeoutDuration", typeof(TimeSpan), typeof(NavigatorWorkflow), new PropertyMetadata(new TimeSpan(0, 30, 0)));

        private static readonly DependencyProperty TimerQueueIdProperty = DependencyProperty.Register("TimerQueueId", typeof(Guid), typeof(NavigatorWorkflow));

        public NavigatorWorkflow() : base()
        {
        }

        public NavigatorWorkflow(string name)
            : base(name)
        {
        }

        [SRDescription(SR.TimeoutDurationDescription)]
        [SRCategory(SR.Activity)]
        [MergableProperty(false)]
        [TypeConverter(typeof(TimeoutDurationConverter))]
        public TimeSpan TimeoutDuration
        {
            get
            {
                return (TimeSpan)base.GetValue(TimeoutDurationProperty);
            }
            set
            {
                base.SetValue(TimeoutDurationProperty, value);
            }
        }

        ReadOnlyCollection<Activity> History
        {
            get
            {
                List<Activity> history = this.GetValue(Navigator.HistoryProperty) as List<Activity>;
                if (history != null)
                {
                    return history.AsReadOnly();
                }
                else
                {
                    return new List<Activity>().AsReadOnly();
                }
            }
        }

        Guid TimerQueueId
        {
            get
            {
                return (Guid)this.GetValue(TimerQueueIdProperty);
            }
        }

        protected override void Initialize(IServiceProvider provider)
        {
            if (this.OwnerPermission != null)
            {
                if (!this.OwnerPermission.CheckPermission())
                {
                    throw this.OwnerPermission.Exception;
                }
            }
            else
            {
                this.OwnerPermission = new Permission(Thread.CurrentPrincipal.Identity.Name, null);
            }

            base.Initialize(provider);

            // setup the timer queue.
            WorkflowQueuingService queueService = provider.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
            Guid timerQueueId = Guid.NewGuid();
            this.SetValue(TimerQueueIdProperty, timerQueueId);
            WorkflowQueue timerQueue = queueService.CreateWorkflowQueue(timerQueueId, false);
            timerQueue.RegisterForQueueItemAvailable(this);
            TimerEventSubscription timerSub = new TimerEventSubscription(this.TimerQueueId, this.WorkflowInstanceId, DateTime.UtcNow + this.TimeoutDuration);
            TimerEventSubscriptionCollection timers = (TimerEventSubscriptionCollection)this.GetValue(TimerEventSubscriptionCollection.TimerCollectionProperty);
            timers.Add(timerSub);
        }

        protected override void OnClosed(IServiceProvider provider)
        {
            // delete the timer queue.
            WorkflowQueuingService queueService = provider.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
            WorkflowQueue timerQueue = queueService.GetWorkflowQueue(this.TimerQueueId);
            timerQueue.UnregisterForQueueItemAvailable(this);
            TimerEventSubscriptionCollection timers = (TimerEventSubscriptionCollection)this.GetValue(TimerEventSubscriptionCollection.TimerCollectionProperty);
            timers.Remove(this.TimerQueueId);
            queueService.DeleteWorkflowQueue(this.TimerQueueId);

            base.OnClosed(provider);
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            List<Activity> history = new List<Activity>();
            this.SetValue(Navigator.HistoryProperty, history);

            return base.Execute(executionContext);
        }

        protected override void ProcessCommand(object cmd, ActivityExecutionContext executionContext)
        {
            if (cmd is GetContextCommand)
            {
                // Get current executing interaction context.
                if (((GetContextCommand)cmd).Bookmark == null)
                {
                    ((GetContextCommand)cmd).Context = Helpers.GetContext(null, this.Current, ActivityExecutionStatus.Executing);
                }
                else
                {
                    // Get history interaction context.
                    EnsureHistory(executionContext);
                    ((GetContextCommand)cmd).Context = Helpers.GetContext(((GetContextCommand)cmd).Bookmark, this.History);
                }
            }
            else if (cmd is GetHistoryCommand)
            {
                EnsureHistory(executionContext);
                ((GetHistoryCommand)cmd).History = Helpers.GetHistoryBookmarks(this.History);
            }
            else if (cmd is SynchronizeCommand)
            {
                EnsureHistory(executionContext);
                this.SetValue(InteractionBaseActivity.InteractionCommandProperty, cmd);
                if (Helpers.GetImmediateChildByName(this, ((SynchronizeCommand)cmd).Bookmark.ActivityName) != null)
                {
                    //cancel the currently executing activity
                    if (this.SynchronizationMode == SynchronizationMode.NotAllowed)
                    {
                        throw new InvalidOperationException(SR.GetString(SR.Error_SyncCommandNotAllowed, this.QualifiedName));
                    }

                    // TODO: check for HistoryOnly mode.

                    if (this.Current != null)
                    {
                        SynchronizeTo(executionContext, ((SynchronizeCommand)cmd));
                    }
                }
                else if (this.Current is InteractionGroupActivity)
                {
                    Navigator navigator = null;
                    foreach (Activity child in ((InteractionGroupActivity)this.Current).EnabledActivities)
                    {
                        if (child is Navigator && child.ExecutionStatus == ActivityExecutionStatus.Executing
                            && Helpers.GetImmediateChildByName(child, ((SynchronizeCommand)cmd).Bookmark.ActivityName) != null)
                        {
                            navigator = child as Navigator;
                            break;
                        }
                    }
                    if (navigator != null)
                    {
                        WorkflowQueuingService qService = executionContext.GetService<WorkflowQueuingService>();
                        WorkflowQueue queue = qService.GetWorkflowQueue(navigator.QualifiedName);
                        if (queue != null)
                        {
                            queue.Enqueue(cmd);
                        }
                    }
                    else
                    {
                        throw new ArgumentException(SR.GetString(SR.Error_CantFindActivity, ((SynchronizeCommand)cmd).Bookmark.ActivityName, this.Current.Name));
                    }
                }
            }
            else
            {
                throw new ArgumentException(SR.GetString(SR.Error_UnknowQueryCommand, cmd.GetType().FullName));
            }
        }

        // Hanles the timer event.
        void IActivityEventListener<QueueEventArgs>.OnEvent(object sender, QueueEventArgs e)
        {
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }

            ActivityExecutionContext context = sender as ActivityExecutionContext;
            if (context == null)
            {
                throw new ArgumentException("sender");
            }

            WorkflowQueuingService qService = context.GetService<WorkflowQueuingService>();
            WorkflowQueue queue = qService.GetWorkflowQueue(e.QueueName);
            if (queue.Count > 0)
            {
                queue.Dequeue();
            }
            else
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_CantDequeueMessage, e.QueueName));
            }

            if (this.Cancel(context) == ActivityExecutionStatus.Closed)
            {
                context.CloseActivity();
            }
        }


        void EnsureHistory(ActivityExecutionContext context)
        {
            List<Activity> history = this.GetValue(HistoryProperty) as List<Activity>;
            if (history == null)
            {
                List<Guid> historyList = this.GetValue(HistoryContextIDsProperty) as List<Guid>;
                Debug.Assert(historyList != null);

                history = new List<Activity>();
                Dictionary<Guid, ActivityExecutionContext> historyContextList = BuildHistoryContextList(context);
                foreach (Guid ctxId in historyList)
                {
                    if (historyContextList.ContainsKey(ctxId))
                    {
                        history.Add(historyContextList[ctxId].Activity);
                    }
                }
                this.SetValue(HistoryProperty, history);
            }
        }

        Dictionary<Guid, ActivityExecutionContext> BuildHistoryContextList(ActivityExecutionContext rootContext)
        {
            Dictionary<Guid, ActivityExecutionContext> historyContextList = new Dictionary<Guid, ActivityExecutionContext>();
            Queue<ActivityExecutionContext> executionContextQueue = new Queue<ActivityExecutionContext>();
            executionContextQueue.Enqueue(rootContext);
            while (executionContextQueue.Count > 0)
            {
                ActivityExecutionContext executionContext = executionContextQueue.Dequeue();
                foreach (ActivityExecutionContext childContext in executionContext.ExecutionContextManager.ExecutionContexts)
                {
                    historyContextList.Add(childContext.ContextGuid, childContext);
                    if (ContainsChildNavigator(childContext.Activity))
                    {
                        executionContextQueue.Enqueue(childContext);
                    }
                }

                foreach (Guid childContextId in executionContext.ExecutionContextManager.PersistedExecutionContexts)
                {
                    //restore the context back, this may come from store or local WF runtime cache
                    //depending on whether there was a previous persist point during the execution
                    ActivityExecutionContext childContext = executionContext.ExecutionContextManager.GetPersistedExecutionContext(childContextId);
                    try
                    {
                        historyContextList.Add(childContext.ContextGuid, childContext);
                        if (ContainsChildNavigator(childContext.Activity))
                        {
                            executionContextQueue.Enqueue(childContext);
                        }
                    }
                    finally
                    {
                        //persist the context back, the persistence wont happen until the next persist point in the program
                        executionContext.ExecutionContextManager.CompleteExecutionContext(childContext, true);
                    }
                }
            }

            return historyContextList;
        }

        bool ContainsChildNavigator(Activity parent)
        {
            bool foundNavigator = false;
            Queue<CompositeActivity> compositeQueue = new Queue<CompositeActivity>();
            if (parent is CompositeActivity)
            {
                compositeQueue.Enqueue(parent as CompositeActivity);
            }

            while (compositeQueue.Count > 0)
            {
                CompositeActivity compositeActivity = compositeQueue.Dequeue();
                foreach (Activity child in compositeActivity.EnabledActivities)
                {
                    if (child is Navigator)
                    {
                        foundNavigator = true;
                        break;
                    }
                    else if (child is CompositeActivity)
                    {
                        compositeQueue.Enqueue(child as CompositeActivity);
                    }
                }
            }

            return foundNavigator;
        }
    }
}

